package com.xbongbong.saas.help;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.paas.domain.entity.PaasFormDataEntity;
import com.xbongbong.paas.domain.entity.PaasFormExplainEntity;
import com.xbongbong.paas.enums.FieldTypeEnum;
import com.xbongbong.paas.enums.ScoreConditionEnum;
import com.xbongbong.paas.field.FieldAttrEntity;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.pro.domain.entity.ScoreOperationEntity;
import com.xbongbong.pro.domain.entity.ScoreRuleConditionEntity;
import com.xbongbong.pro.domain.entity.ScoreRuleEntity;
import com.xbongbong.pro.enums.ErrorCodeEnum;
import com.xbongbong.pro.enums.errorcodes.ScoreErrorCodeEnum;
import com.xbongbong.pro.score.pojo.vo.ScoreFieldConditionVO;
import com.xbongbong.pro.score.pojo.vo.ScoreRuleDetailVO;
import com.xbongbong.saas.enums.ScoreOperationTypeEnum;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.ClueEnum;
import com.xbongbong.saas.enums.business.CustomerManagementEnum;
import com.xbongbong.saas.factory.score.ScoreConditionFactory;
import com.xbongbong.saas.model.ScoreRuleModel;
import com.xbongbong.saas.service.ScoreConditionService;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;


/**
 * 评分计算
 *
 * @author chunyang.qin
 * @version v1.0
 * @date 2020/11/30 17:18
 * @since v1.0
 */
@Component
public class ScoreCalculateHelp {

    private static final Logger LOG = LoggerFactory.getLogger(ScoreCalculateHelp.class);
    private static final Double DEFAULT_SCORE_VALUE = 0D;


    @Resource
    private ScoreRuleModel scoreRuleModel;
    @Resource
    private ScoreRuleHelp scoreRuleHelp;
    @Resource
    private ScoreConditionFactory scoreConditionFactory;
    @Resource
    private MarketManagementBusinessHelp marketManagementBusinessHelp;

    /**
     *
      * @param xcXbbRefTypeEnum
     * @param paasFormDataEntity
     * @param pasExplainEntity 表单对应的解释
     * @param scoreRuleEntity 表单对应的评分规则
     * @return
     */
    public Double calculateSingleDataScore(XbbRefTypeEnum xcXbbRefTypeEnum,PaasFormDataEntity paasFormDataEntity,PaasFormExplainEntity pasExplainEntity,ScoreRuleEntity scoreRuleEntity) {
        if (Objects.nonNull(paasFormDataEntity) && Objects.nonNull(pasExplainEntity) && Objects.nonNull(scoreRuleEntity)) {
            try {
                //查询解释
                List<FieldAttrEntity> explainList = JSONArray.parseArray(pasExplainEntity.getExplains(), FieldAttrEntity.class);
                if (Objects.isNull(scoreRuleEntity)) {
                    throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200012, ScoreErrorCodeEnum.API_ERROR_1200012.getMsg());
                }
                ScoreRuleDetailVO scoreRuleDetailVO = scoreRuleHelp.packageScoreRuleDetailVO(scoreRuleEntity);
                //计算评分
                Double score = calculateDataScore(xcXbbRefTypeEnum,paasFormDataEntity, scoreRuleDetailVO, explainList);
                return score;
            } catch (Exception e) {
                LOG.error("数据计算评分异常,异常原因-{}-", e.getMessage());
                return null;
            }
        }
        return null;
    }


    /**
     * 单条数据计算评分
     * businessType: 参与评分的数据的业务类型
     * paasFormDataEntity : 需要评分的数据
     * scoreRule：评分规则
     * explainList： 该条数据对应的解释
     *
     * @return null 表示没有开始评分; 0 表示是参与了评分，只是分数算出来是 0分
     */
    public Double calculateDataScore(XbbRefTypeEnum xcXbbRefTypeEnum , PaasFormDataEntity paasFormDataEntity, ScoreRuleDetailVO scoreRule, List<FieldAttrEntity> explainList) throws XbbException {
        if (Objects.isNull(paasFormDataEntity) || Objects.isNull(scoreRule) || CollectionsUtil.isEmpty(explainList)) {
            LOG.error("不计算评分，原因：paasFormDataEntity,scoreRule,explainList 三个条件都不能为空");
            return null;
        }
        //分数值为 null 的意义在于，表示未评分，不用去更新客户数据或者线索数据
        Double scoreValue = null;
        //行为评分权重，画像评分权重（ScoreRuleDetailVO 是处理过小数的，需要转回小数）
        Double actionScoreRate = scoreRule.getActionScoreRate() * 0.01;
        Double portraitScoreRate = scoreRule.getPortraitScoreRate() * 0.01;
        //评分规则条件
        List<ScoreFieldConditionVO> actionScoreRule = scoreRule.getActionScoreRule();
        List<ScoreFieldConditionVO> portraitScoreRule = scoreRule.getPortraitScoreRule();
        //画像规则 和 行为规则同时为空时，返回0分
        if(CollectionUtils.isEmpty(actionScoreRule) && CollectionUtils.isEmpty(portraitScoreRule)){
            return 0D;
        }
        //获取对象值
        JSONObject data = paasFormDataEntity.getData();
        if (data == null) {
            return null;
        }
        //data外层数据处理
        //两个作用：  （1）评分统一处理data内的数据，把data外层的数据(eg：addTime ) 先放入内层，因为下面算分获取值的方式都是 data.get(key) 的方式
         //          (2)  骚东西处理，比如："最后跟进时间"，在系统里面看起来是空，但是实际上存了一个0。这样在算分判断时间为空时，就不准确了
        //重点： todo 算分完成之后，在 removeSpecialFiledAttrFromData 方法中移除上面注释说明添加的key,这样就还原了data数据，不会影响数据更新
        addSpecialFiled2Data(xcXbbRefTypeEnum,paasFormDataEntity, data);
        //开始匹配评分
        //画像评分总分
        Double actionSumScore = 0D;
        //行为评分总分
        Double portraitSumScore = 0D;
        //Set<String> dataAttrList = data.keySet();
        //data得分 = sum(每个字段的对应的规则得分)
        for(FieldAttrEntity fieldAttrEntity : explainList){
            Object dataValue = data.get(fieldAttrEntity.getAttr());
            //行为评分
            actionSumScore += calculateSingleFieldRuleSumScore(fieldAttrEntity, dataValue, actionScoreRule);
            //画像评分
            portraitSumScore += calculateSingleFieldRuleSumScore(fieldAttrEntity, dataValue, portraitScoreRule);
        }
        //实际分数=行为评分*行为评分权重 + 画像评分*画像评分权重
        scoreValue = (actionSumScore * actionScoreRate) + (portraitSumScore * portraitScoreRate);
        //移除 创建时间,最后跟进时间等骚操作
        removeSpecialFiledAttrFromData(xcXbbRefTypeEnum,data);
        //保留两位小数,四舍五入
        return new BigDecimal(scoreValue).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }


    /**
     * 判断列表快速编辑的字段是否属于该表单对应的"评分规则"字段
     * @return
     */
    public Boolean isTriggerScoreForQuickEdit(List<String> attrList, String corpid, Long fromId) {
        try {
            //套餐是标准版 或者 表单下没有启用的评分规则。不触发评分更新
            if(marketManagementBusinessHelp.isStandardWithFeeType(corpid) || !(scoreRuleHelp.haveEnabledScoreRule(corpid,fromId))){
                return false;
            }
            ScoreRuleEntity scoreRuleEntity = scoreRuleModel.getByFormId(fromId, corpid);
            ScoreRuleDetailVO scoreRuleDetailVO = scoreRuleHelp.packageScoreRuleDetailVO(scoreRuleEntity);
            List<ScoreFieldConditionVO> actionScoreRule = scoreRuleDetailVO.getActionScoreRule();
            List<ScoreFieldConditionVO> portraitScoreRule = scoreRuleDetailVO.getPortraitScoreRule();
            if(CollectionUtils.isNotEmpty(actionScoreRule)){
                //行为评分包含 更新时间 直接触发评分
                for(ScoreFieldConditionVO scoreFieldConditionVO : actionScoreRule){
                    if(Objects.equals(FieldTypeEnum.UPDATETIME.getType(),scoreFieldConditionVO.getFieldType())){
                        return true;
                    }
                }
                if(isHitScoreRuleFiled(attrList,actionScoreRule)){
                    return true;
                }
            }
            if(CollectionUtils.isNotEmpty(portraitScoreRule)){
                if(isHitScoreRuleFiled(attrList,portraitScoreRule)){
                    return true;
                }
            }
        }catch (Exception e){
            LOG.error("isTriggerScoreForQuickEdit 发生异常,入参corpid-{}-,formId-{}-",corpid,JSON.toJSONString(fromId));
        }
        return false;
    }



    /**
     * data里面一个字段对应一种规则 计算的总分，eg: text_1 命中 画像评分规则 后的的分数
     *
     * @param fieldAttrEntity
     * @param dataValue
     * @param scoreRule
     * @param
     * @return
     */
    private Double calculateSingleFieldRuleSumScore(FieldAttrEntity fieldAttrEntity, Object dataValue, List<ScoreFieldConditionVO> scoreRule) {
        Double singleRuleSumScore = 0D;
        if (CollectionUtils.isNotEmpty(scoreRule)) {
            try {
                //根据字段类型，获取评分实现类
                FieldTypeEnum fieldTypeEnum = FieldTypeEnum.getFieldTypeEnum(fieldAttrEntity.getFieldType());
                //单个规则总分  = 各个字段的分数总和
                String fieldAttr = fieldAttrEntity.getAttr();
                ScoreConditionService scoreConditionService = scoreConditionFactory.getScoreConditionService(fieldTypeEnum);
                for (ScoreFieldConditionVO scoreFieldConditionVO : scoreRule) {
                    singleRuleSumScore += calculateSingleFieldScore(fieldAttr, scoreConditionService, dataValue, scoreFieldConditionVO);
                }
            } catch (Exception e) {
                return DEFAULT_SCORE_VALUE;
            }
        }
        return singleRuleSumScore;
    }


    //计算单个字段得分
    private Double calculateSingleFieldScore(String fieldAttr, ScoreConditionService scoreConditionService, Object dataValue, ScoreFieldConditionVO scoreFieldConditionVO) {
        Double singleFieldScore = 0D;
        try {
            //字段命中,开始计算这个字段的分数
            if (fieldAttr.equals(scoreFieldConditionVO.getAttr())) {
                List<ScoreRuleConditionEntity> scoreConditionList = scoreFieldConditionVO.getScoreConditionList();
                //该字段的得分 = 该字段命中的条件的分 的总和
                for (ScoreRuleConditionEntity scoreRuleConditionEntity : scoreConditionList) {
                    //计算单个条件得分
                    List<Object> compareValue = scoreRuleConditionEntity.getValue();
                    String symbol = scoreRuleConditionEntity.getSymbol();
                    ScoreOperationEntity scoreOperation = scoreRuleConditionEntity.getScoreOperation();
                    ScoreOperationTypeEnum scoreOperationTypeEnum = ScoreOperationTypeEnum.getByCode(scoreOperation.getCode());
                    Double scoreOperationValue = scoreOperation.getValue();
                    ScoreConditionEnum scoreConditionEnum = ScoreConditionEnum.getConditionEnum(symbol);
                    // 分数叠加
                    singleFieldScore += calculateSingleConditionScore(scoreConditionEnum, scoreConditionService, dataValue, compareValue, scoreOperationTypeEnum, scoreOperationValue);
                }
            }
        } catch (Exception e) {
            return DEFAULT_SCORE_VALUE;
        }
        return singleFieldScore;
    }

    /**
     * 一个条件的得分
     *
     * @param scoreConditionEnum
     * @param scoreConditionService
     * @param dataValue
     * @param compareValue
     * @param scoreOperationTypeEnum
     * @param scoreOperationValue
     * @return
     */
    private Double calculateSingleConditionScore(ScoreConditionEnum scoreConditionEnum, ScoreConditionService scoreConditionService, Object dataValue, List<Object> compareValue, ScoreOperationTypeEnum scoreOperationTypeEnum, Double scoreOperationValue) {
        Double scoreValue = 0D;
        switch (scoreConditionEnum) {
            case EMPTY:
                scoreValue = scoreConditionService.empty(dataValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case NOEMPTY:
                scoreValue = scoreConditionService.noempty(dataValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case EQUAL:
                scoreValue = scoreConditionService.equal(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case NOEQUAL:
                scoreValue = scoreConditionService.noequal(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case GREATEREQUAL:
                scoreValue = scoreConditionService.greaterequal(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case GREATERTHAN:
                scoreValue = scoreConditionService.greatethan(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case LESSEQUAL:
                scoreValue = scoreConditionService.lessequal(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case LESSTHAN:
                scoreValue = scoreConditionService.lessthan(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case RANGE:
                scoreValue = scoreConditionService.range(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case INCLUDE:
                scoreValue = scoreConditionService.include(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            case ALLINCLUDE:
                scoreValue = scoreConditionService.allinclude(dataValue, compareValue, scoreOperationValue, scoreOperationTypeEnum);
                break;
            default:
                break;
        }
        return scoreValue;
    }


    /**
     * 调用此方法，请对explainList做空校验
     *
     * @param explainList
     * @return
     */
    private Map<String, FieldAttrEntity> getExplainMap(List<FieldAttrEntity> explainList) {
        Map<String, FieldAttrEntity> map = new HashMap<>(explainList.size());
        for (FieldAttrEntity field : explainList) {
            map.put(field.getAttr(), field);
        }
        return map;
    }

    /**
     * 处理特殊字段
     * 该方法的作用主要是：
     *(1) 把 外层的 addTime 先存到data内层，因为后面评分计算时 编辑 data的key的
     * (2)处理一掉一些字段的默认值，这个很坑爹，目前发现特殊字段的有下面几个：
     *      (2.1) 最后跟进时间。为空时，的默认值是 0，这会导致有“为空”条件判断时，不准确。这里处理为 “”
     *      (2.2) 客户的流失原因 和 线索的无效原因。为空时，默认值是0，这里处理为“”
     * @param xcXbbRefTypeEnum
     * @param paasFormDataEntity
     * @param data
     * @throws XbbException
     */
    private void addSpecialFiled2Data(XbbRefTypeEnum xcXbbRefTypeEnum , PaasFormDataEntity paasFormDataEntity, JSONObject data) throws XbbException {

        //外层的创建时间，更新时间,放入data内层
        Long addTime = paasFormDataEntity.getAddTime();
        Long updateTime = paasFormDataEntity.getUpdateTime();
        data.put("addTime", addTime);
        data.put("updateTime", updateTime);

        //最后跟进时间.把最后跟进时间 为0，暂时处理为“”，否则在条件判空时，就不准确。（saasDataInitHelp 这个方法赋初始值时，会给一些骚操作，后续要注意这个坑）
        String lastConnectTimeAttr;
        Object lastConnectTime = null;
        //流失原因为0时，暂时处理为"",，否则在条件判空时，就不准确。
        String wastageAttr = "";
        Object wastage = null;
        //线索无效原因
        String invalidReasonAttr = "";
        Object invalidReason = null;
        switch (xcXbbRefTypeEnum){
            case CUSTOMER_MANAGEMENT:
                //最后跟进时间
                lastConnectTimeAttr= CustomerManagementEnum.LAST_CONNECT_TIME.getAttr();
                lastConnectTime  = data.get(CustomerManagementEnum.LAST_CONNECT_TIME.getAttr());
                //流失原因
                wastageAttr= CustomerManagementEnum.WASTAGE.getAttr();
                wastage  = data.get(CustomerManagementEnum.WASTAGE.getAttr());
                break;
            case CLUE:
                lastConnectTimeAttr= ClueEnum.LAST_CONNECT_TIME.getAttr();
                lastConnectTime  = data.get(ClueEnum.LAST_CONNECT_TIME.getAttr());
                //无效原因
                invalidReasonAttr= ClueEnum.INVALID_REASON.getAttr();
                invalidReason  = data.get(ClueEnum.INVALID_REASON.getAttr());
                break;
            default:
                throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200001, ScoreErrorCodeEnum.API_ERROR_1200001.getMsg());
        }
        if(Objects.nonNull(lastConnectTime) && (lastConnectTime instanceof Number) && ((Number) lastConnectTime).intValue() == 0){
            data.put(lastConnectTimeAttr,"");
        }
        if(Objects.nonNull(wastage) && Objects.equals(String.valueOf(wastage),"0")){
            data.put(wastageAttr,"");
        }
        if(Objects.nonNull(invalidReason) && Objects.equals(String.valueOf(invalidReason),"0")){
            data.put(invalidReasonAttr,"");
        }
    }
    /**
     * 移除掉 addSpecialFiled2Data 中的骚操作
     *
     * @param data
     */
    private void removeSpecialFiledAttrFromData(XbbRefTypeEnum xbbRefTypeEnum , JSONObject data) throws XbbException {
        //移除内层 addTime
        data.remove("addTime");
        data.remove("updateTime");

        //还原“最后跟进时间”为0
        String lastConnectTimeAttr;
        Object lastConnectTime;

        //还原"流失原因"为0
        String wastageAttr = "";
        Object wastage = null;

        //还原"流失原因"为0
        String invalidReasonAttr = "";
        Object invalidReason = null;

        switch (xbbRefTypeEnum){
            case CUSTOMER_MANAGEMENT:
                lastConnectTimeAttr= CustomerManagementEnum.LAST_CONNECT_TIME.getAttr();
                lastConnectTime  = data.get(ClueEnum.LAST_CONNECT_TIME.getAttr());
                //客户流失原因
                wastageAttr= CustomerManagementEnum.WASTAGE.getAttr();
                wastage  = data.get(CustomerManagementEnum.WASTAGE.getAttr());
                break;
            case CLUE:
                lastConnectTimeAttr= ClueEnum.LAST_CONNECT_TIME.getAttr();
                lastConnectTime  = data.get(ClueEnum.LAST_CONNECT_TIME.getAttr());
                //线索无效原因
                wastageAttr= ClueEnum.INVALID_REASON.getAttr();
                wastage  = data.get(ClueEnum.INVALID_REASON.getAttr());
                break;
            default:
                throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200001, ScoreErrorCodeEnum.API_ERROR_1200001.getMsg());
        }
        if(Objects.nonNull(lastConnectTime) && String.valueOf(lastConnectTime).equals("") ){
            data.put(lastConnectTimeAttr,0);
        }
        if(StringUtil.isNotEmpty(wastageAttr) && Objects.nonNull(wastage) && String.valueOf(wastage).equals("") ){
            data.put(wastageAttr,"0");
        }
        if(StringUtil.isNotEmpty(invalidReasonAttr) && Objects.nonNull(invalidReason) && String.valueOf(invalidReason).equals("") ){
            data.put(invalidReasonAttr,"0");
        }

    }


    /**
     *
     * 快速编辑。判断attr是否属于评分规则中的字段
     * @return
     */
    private Boolean isHitScoreRuleFiled(List<String> attrList, List<ScoreFieldConditionVO> scoreFieldConditionVOList) {
        if (CollectionUtils.isEmpty(scoreFieldConditionVOList) || CollectionUtils.isEmpty(attrList)) {
            return false;
        }
        for (ScoreFieldConditionVO scoreFieldConditionVO : scoreFieldConditionVOList) {
            for (String attr : attrList) {
                if (Objects.isNull(attr)) {
                    return false;
                }
                if (attr.equals(scoreFieldConditionVO.getAttr())) {
                    return true;
                }
            }
        }
        return false;
    }

}
